#include <crt_util.h>
#include "compat_event_util.h"

#if (TARGET_OS == OS_WIN)

#include "compat_event_iocp.h"
#include "compat_event_win32.h"
#include <mswsock.h>
#include <winsock2.h>
#include <ws2tcpip.h>

static int iocp_set_wsasock_accept_oper(evoper_t* accept_oper, lpwsasock sock)
{
    rc_error(accept_oper != NULL, S_ERROR);
    rc_error(sock != NULL, S_ERROR);
    sock->accept_oper = accept_oper;
    return S_SUCCESS;
}
static int oper_overlapped_memset(evoper_t* oper)
{
    rc_error(oper != NULL, S_ERROR);
    lpevoper_ov ov = NULL;
    ov = (lpevoper_ov)evoper_private(oper);
    rc_error(ov != NULL, S_ERROR);
    s_memset(&ov->overlapped, 0, sizeof(OVERLAPPED));
    return S_SUCCESS;
}
int win32_sock_connectexed(evoper_t* oper)
{
    int rc = S_SUCCESS;
    int sec;
    int bytes = sizeof(sec);
    int res = 0;

    res = getsockopt(oper->sock, SOL_SOCKET, SO_CONNECT_TIME, (char*)&sec, (PINT)&bytes);
    if (res != NO_ERROR) {
        rc = S_ERROR;
    } else {
        if (sec == 0xFFFFFFFF) {
            rc = S_ERROR;
        } else
            rc = S_SUCCESS;
    }
    return rc;
}

int win32_sock_connect_ctx(evoper_t* oper)
{
    int res = 0;
    rc_error(oper != NULL, S_ERROR);
    res = setsockopt(oper->sock, SOL_SOCKET, SO_UPDATE_CONNECT_CONTEXT, NULL, 0);
    return res != NO_ERROR ? S_ERROR : S_SUCCESS;
}

int win32_sock_accpet_ctx(evoper_t* oper, evoper_t* oper_s)
{
    int res = 0;
    rc_error(oper != NULL, S_ERROR);
    res = setsockopt(oper_s->sock, SOL_SOCKET, SO_UPDATE_ACCEPT_CONTEXT, (const char*)&oper->sock, sizeof(int));
    return res != NO_ERROR ? S_ERROR : S_SUCCESS;
}
static int win32_sock_connectex(evoper_t* oper, const struct sockaddr* sa, socklen_t socklen)
{
    BOOL ret = FALSE;
    int rc = S_ERROR;
    rc_error(oper != NULL, S_ERROR);
    iocp_op_t* base = (iocp_op_t*)evoper_evbase(oper);
    rc_error(base != NULL, S_ERROR);
    lpevoper_ov ov = (lpevoper_ov)evoper_private(oper);
    rc_error(ov != NULL, S_ERROR);
    rc_error(base->fns.ConnectEx != NULL, S_ERROR);

    struct s_sockaddr_storage addr;
    s_memset(&addr, 0, sizeof(addr));

    if (sa->sa_family == AF_INET) {
        struct sockaddr_in* sin = (struct sockaddr_in*)&addr;
        sin->sin_family = AF_INET;
        sin->sin_addr.s_addr = htonl(INADDR_ANY);
        sin->sin_port = htons(0);
    } else if (sa->sa_family == AF_INET6) {
        struct sockaddr_in6* sin6 = (struct sockaddr_in6*)&addr;
        sin6->sin6_family = AF_INET6;
        sin6->sin6_addr = in6addr_any;
        sin6->sin6_port = htons(0);
    } else {
        return S_ERROR;
    }

    if (bind(oper->sock, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
        if (WSAGetLastError() != WSAEINVAL)
            return S_ERROR;
    }

    ret = base->fns.ConnectEx(oper->sock, sa, socklen, 0, 0, 0, &ov->overlapped);
    crterr err = crt_geterror();

    if (ret == TRUE) {
        win32_sock_connect_ctx(oper);
        rc = S_COMPLETED;
    } else if (ret == FALSE && ERROR_IO_PENDING == err) {
        rc = S_PENDING;

    } else {
        rc = S_ERROR;
    }

    return rc;
}
int win32_sock_connect(evoper_t* oper, const evaddr_t* addr, ev_time_t tv)
{
    oper_overlapped_memset(oper);

    struct sockaddr_in saddr;
    s_memset(&saddr, 0, sizeof(struct sockaddr_in));

    saddr.sin_family = PF_INET;
    saddr.sin_addr.s_addr = inet_addr(addr->ptr);
    saddr.sin_port = htons(addr->port);

    return win32_sock_connectex(oper, (struct sockaddr*)&saddr, sizeof(saddr));
}

int win32_sock_accept(evoper_t* accept_oper, wsasock* sk)
{
    int rc = S_ERROR;
    BOOL ret = FALSE;
    DWORD len = 0;
    rc_error(accept_oper != NULL, S_ERROR);
    iocp_op_t* base = (iocp_op_t*)evoper_evbase(accept_oper);
    rc_error(base != NULL, S_ERROR);
    rc_error(base->fns.AcceptEx != NULL, S_ERROR);

    evoper_t* oper = sk->oper;
    rc_error(oper != NULL, S_ERROR);

    lpevoper_ov obj = (evoper_ov*)evoper_private(oper);

    rc = iocp_set_wsasock_accept_oper(accept_oper, sk);
    rc_error(rc != S_ERROR, S_ERROR);

    rc = oper_overlapped_memset(accept_oper);
    rc_error(rc != S_ERROR, S_ERROR);

    ret = base->fns.AcceptEx(accept_oper->sock, oper->sock, obj->addr, 0, ACCEPT_ADDRESS_LENGTH, ACCEPT_ADDRESS_LENGTH, &len, &obj->overlapped);

    crterr err = crt_geterror();

    if (ret == TRUE) {
        rc = S_COMPLETED;
    } else if (ret == FALSE && ERROR_IO_PENDING == err) {
        rc = S_PENDING;
    } else {
        rc = S_ERROR;
    }

    return rc;
}
static int wsa_sock_recv(evoper_t* oper, int* ret, sock_size* bytes)
{
    int rc = S_ERROR;
    DWORD bytes_transferred = 0;
    DWORD flags = 0;
    WSABUF wsabuf;

    lpevoper_ov ov = (lpevoper_ov)evoper_private(oper);
    rc_error(ov != NULL, S_ERROR);

    rc = oper_overlapped_memset(oper);
    rc_error(rc != S_ERROR, S_ERROR);

    buf_ptr ptr = NULL;
    buf_len len = 0;

    rc = evbuf_get_buf(&oper->rbuf, &ptr);
    rc_error(rc != S_ERROR, S_ERROR);

    rc = evbuf_get_len(&oper->rbuf, &len);
    rc_error(rc != S_ERROR, S_ERROR);

    wsabuf.buf = ptr;
    wsabuf.len = len & 0xFFFFFFFF;

    /*
     * if a socket was bound with I/O completion port
     * then GetQueuedCompletionStatus() would anyway return its status
     * despite that WSARecv() was already complete
     */
    ov->r = ev_on;
    *ret = WSARecv(oper->sock, &wsabuf, 1, &bytes_transferred, &flags, &ov->overlapped, NULL);
    *bytes = bytes_transferred;
    return rc;
}
static int wsa_sock_send(evoper_t* oper, int* ret, sock_size* bytes)
{
    int rc = S_ERROR;
    DWORD bytes_transferred = 0;
    DWORD flags = 0;
    WSABUF wsabuf;

    rc_error(oper != NULL, S_ERROR);
    lpevoper_ov ov = (lpevoper_ov)evoper_private(oper);
    rc_error(ov != NULL, S_ERROR);

    rc = oper_overlapped_memset(oper);
    rc_error(rc != S_ERROR, S_ERROR);

    buf_ptr ptr = NULL;
    buf_len len = 0;

    rc = evbuf_get_buf(&oper->wbuf, &ptr);
    rc_error(rc != S_ERROR, S_ERROR);

    rc = evbuf_get_len(&oper->wbuf, &len);
    rc_error(rc != S_ERROR, S_ERROR);

    wsabuf.buf = ptr;
    wsabuf.len = len & 0xFFFFFFFF;

    /*
     * if a socket was bound with I/O completion port then
     * GetQueuedCompletionStatus() would anyway return its status
     * despite that WSASend() was already complete
     */
    ov->w = ev_on;
    *ret = WSASend(oper->sock, &wsabuf, 1, &bytes_transferred, flags, &ov->overlapped, NULL);
    *bytes = bytes_transferred;
    return rc;
}

int win32_sock_read_op(evoper_t* oper)
{
    int ret = 0;
    int rc = S_ERROR;
    sock_size byte = 0;
    crterr err = 0;
    rc_error(oper != NULL, S_ERROR);
    rc = wsa_sock_recv(oper, &ret, &byte);
    err = crt_geterror();

    if (ret == SOCKET_ERROR) {
        if (err == ERROR_NETNAME_DELETED) {
            rc = S_ERROR;
            goto end;
        } else if (err == ERROR_PORT_UNREACHABLE) {
            rc = S_ERROR;
            goto end;
        }
    }

    if (ret == 0) {
        evbuf_op_off(&oper->rbuf, byte);
        if (is_evbuf_compelete(&oper->wbuf) == S_SUCCESS) {
            rc = S_COMPLETED;
            goto end;
        }
    }

    if (err == WSA_IO_PENDING) {
        rc = S_PENDING;
        goto end;
    }

    if (byte == 0) {
        evoper_eof_op(oper, ev_on);
        rc = S_ERROR;
        goto end;
    }

    return S_ERROR;
end:
    return rc;
}

int win32_sock_write_op(evoper_t* oper)
{
    int ret = 0;
    int rc = S_ERROR;
    sock_size byte = 0;
    crterr err = 0;
    rc_error(oper != NULL, S_ERROR);

    rc = wsa_sock_send(oper, &ret, &byte);

    if (ret == SOCKET_ERROR) {
        err = crt_geterror();

        if (err == WSAEWOULDBLOCK) {
        } else if (err == WSAECONNABORTED || err == WSAECONNRESET) {
            // Connection aborted or reset, close and reconnect
            rc = S_ERROR;
        } else if (err == WSAENETDOWN || err == WSAENETUNREACH) {
            // Network malfunction, check network connection
            rc = S_ERROR;
        } else if (err == WSAETIMEDOUT) {
            // Connection timeout, try reconnecting or increasing timeout
            rc = S_ERROR;
        } else if (err == WSAECONNREFUSED) {
            // Connection rejected, ensure that the server is running and accepting the connection
            rc = S_ERROR;
        } else if (err == ERROR_PORT_UNREACHABLE) {
            rc = S_ERROR;
        }
    }

    if (ret == 0) {
        if (err == WSA_IO_PENDING) {
            rc = S_PENDING;
            goto end;
        }

        evbuf_op_off(&oper->wbuf, byte);
        if (is_evbuf_compelete(&oper->wbuf) == S_SUCCESS) {
            rc = S_PENDING;
            goto end;
        }
    }

    if (err == WSA_IO_PENDING) {
        rc = S_PENDING;
        goto end;
    }

    return S_ERROR;
end:
    return rc;
}
#endif
